package com.example.xposed;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Criteria;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.CellIdentityCdma;
import android.telephony.CellIdentityGsm;
import android.telephony.CellLocation;
import android.telephony.TelephonyCallback;
import android.util.Log;
import android.widget.Toast;

import com.example.xposed.hook.AbstractHook;
import com.example.xposed.hook.Hook;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Executor;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;

public class UseGps {
    public static final String TAG = "UseGpsHook";
    static boolean isOpen = true;
    public static Double latitude = 0d;
    public static Double longitude = 0d;
    public static Double lastlatitude = -1d;
    public static Double lastlongitude = -1d;
    private static boolean isInit = false;
    private static boolean isGetLocation = false;
    public static String action = BuildConfig.APPLICATION_ID + ".LOCATION";

    public static void hkGPS(ClassLoader classLoader) {
        XposedHelpers.findAndHookMethod("android.app.Application", classLoader, "onCreate", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isInit) {
                    isInit = true;
                    Log.d(TAG, "android.app.Application: onCreate");
                    Context context = (Context) param.thisObject;
                    try {
                        Bundle call = context.getContentResolver().call(Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".LocationManager"), "", "", null);
                        if (call != null) {
                            latitude = call.getDouble("GPSLatitude", 0);
                            longitude = call.getDouble("GPSLongitude", 0);
                        }
                        Toast.makeText(context, longitude + " , " + latitude, Toast.LENGTH_LONG).show();
                        Log.d(TAG, "locationInit: latitude=" + latitude + "  longitude=" + longitude);

                    } catch (Exception e) {
                        Toast.makeText(context, "位置模拟失败,请打开应用", Toast.LENGTH_LONG).show();
                        try {
                            context.registerReceiver(new BroadcastReceiver() {
                                @Override
                                public void onReceive(Context context, Intent intent) {

                                    lastlatitude = latitude;
                                    lastlongitude = longitude;
                                    latitude = intent.getDoubleExtra("GPSLatitude", 0);
                                    longitude = intent.getDoubleExtra("GPSLongitude", 0);
                                    if (!isGetLocation) {
                                        Toast.makeText(context, longitude + " , " + latitude, Toast.LENGTH_LONG).show();
                                        Log.d(TAG, "locationInit: latitude=" + latitude + "  longitude=" + longitude);
                                    }
                                    isGetLocation = true;
                                }
                            }, new IntentFilter(action));
                            Intent intent = new Intent();
                            intent.setPackage(BuildConfig.APPLICATION_ID);
                            intent.setClassName(BuildConfig.APPLICATION_ID, LocationService.class.getName());
                            context.startService(intent);

                        } catch (Exception ignored) {
                        }
                    }


                }


            }
        });
        hookGps(classLoader);

        new Hook("android.telephony.TelephonyManager", classLoader) {

            @Override
            protected boolean beforeMethod(MethodHookParam param) {
                if (param.method.getName().equals("listen")) {
                    Log.d(TAG, "beforeMethod: listen");
                    param.setResult(null);
                }
                if (param.method.getName().equals("registerTelephonyCallback")) {
                    Log.d(TAG, "beforeMethod: registerTelephonyCallback");
                    param.setResult(null);
                }
                return super.beforeMethod(param);
            }

            @Override
            protected boolean afterMethod(MethodHookParam param) {
                if (param.method.getName().equals("getTelephonyProperty")) {
                    Log.d(TAG, "afterMethod: getTelephonyProperty " + param.getResult());
                    param.setResult("");
                }
                if (param.method.getName().equals("getNetworkOperator")) {
                    Log.d(TAG, "afterMethod: getNetworkOperator " + param.getResult());
                    param.setResult("");
                }
                if (param.method.getName().equals("getNetworkOperatorName")) {
                    Log.d(TAG, "afterMethod: getNetworkOperatorName " + param.getResult());
                    param.setResult("");
                }
                if (param.method.getName().equals("getAllCellInfo")) {
                    Log.d(TAG, "afterMethod: getAllCellInfo " + param.getResult());
                    param.setResult(new ArrayList<>());
                } else if (param.method.getName().equals("getCellLocation")) {
                    Log.d(TAG, "afterMethod: getCellLocation" + param.getResult());
                    param.setResult(null);
                } else if (param.method.getName().equals("getNeighboringCellInfo")) {
                    Log.d(TAG, "afterMethod: getNeighboringCellInfo" + param.getResult());
                    param.setResult(new ArrayList<>());
                } else if (param.method.getName().equals("getSimOperator")) {
                    Log.d(TAG, "afterMethod: getSimOperator" + param.getResult());
                    param.setResult("");
                }else if (param.method.getName().equals("getSimCountryIso")) {
                    Log.d(TAG, "afterMethod: getSimCountryIso" + param.getResult());
                    param.setResult("");
                }
                return super.afterMethod(param);
            }

            @Override
            public void hook() {
                hookAllMethod("getTelephonyProperty");
                hookAllMethod("getNetworkOperator");
                hookAllMethod("getNetworkOperatorName");
                hookAllMethod("getAllCellInfo");
                hookAllMethod("getCellLocation");
                hookAllMethod("getNeighboringCellInfo");
                hookAllMethod("listen");
                hookAllMethod("registerTelephonyCallback");
                hookAllMethod("getSimOperator");
                hookAllMethod("getSimCountryIso");

            }
        }.safeHook();

        new Hook("android.sysprop.TelephonyProperties", classLoader) {

            @Override
            protected boolean afterMethod(MethodHookParam param) {
                if (param.method.getName().equals("operator_alpha")) {
                    Log.d(TAG, "afterMethod: operator_alpha " + param.getResult());
                    param.setResult(null);
                } else if (param.method.getName().equals("operator_numeric")) {
                    Log.d(TAG, "afterMethod: operator_numeric " + param.getResult());
                    param.setResult(null);
                }


                return super.afterMethod(param);
            }

            @Override
            public void hook() {
                hookAllMethod("operator_alpha");
                hookAllMethod("operator_numeric");
            }
        }.safeHook();

        new Hook("com.android.internal.telephony.ITelephony$Stub$Proxy", classLoader) {


            @Override
            protected boolean beforeMethod(MethodHookParam param) {

                return super.beforeMethod(param);
            }

            @Override
            protected boolean afterMethod(MethodHookParam param) {
                if (param.method.getName().equals("getAllCellInfo")) {
                    Log.d(TAG, "afterMethod: getAllCellInfo " + param.getResult());
                    param.setResult(new ArrayList<>());
                } else if (param.method.getName().equals("getCellLocation")) {
                    Log.d(TAG, "afterMethod: getCellLocation" + param.getResult());
                    param.setResult(null);
                } else if (param.method.getName().equals("getNeighboringCellInfo")) {
                    Log.d(TAG, "afterMethod: getNeighboringCellInfo" + param.getResult());
                    param.setResult(new ArrayList<>());
                }
                return super.afterMethod(param);
            }

            @Override
            public void hook() {
                hookAllMethod("getAllCellInfo");
                hookAllMethod("getCellLocation");
                hookAllMethod("getNeighboringCellInfo");
            }
        }.safeHook();


        new Hook("android.telephony.TelephonyRegistryManager") {

            @Override
            protected boolean beforeMethod(MethodHookParam param) {
                if (param.method.getName().equals("registerTelephonyCallback")) {
                    Log.d(TAG, "beforeMethod: registerTelephonyCallback");
                    param.setResult(null);
                } else if (param.method.getName().equals("listenFromListener")) {
                    Log.d(TAG, "beforeMethod: listenFromListener");
                    param.setResult(null);
                }else if (param.method.getName().equals("listenFromCallback")){
                    Log.d(TAG, "beforeMethod: listenFromCallback");
                    param.setResult(null);
                }
                return super.beforeMethod(param);
            }

            @Override
            public void hook() {
                hookAllMethod("registerTelephonyCallback");
                hookAllMethod("listenFromListener");
                hookAllMethod("listenFromCallback");
            }
        }.safeHook();
        if (Build.VERSION.SDK_INT>=31) {
            new AbstractHook("android.telephony.TelephonyCallback", classLoader) {


                @Override
                public IHook hookAbstract(Class aclass, ClassLoader classLoader) {

                    return new Hook(aclass.getName(), classLoader) {


                        @Override
                        protected boolean beforeMethod(MethodHookParam param) {
                            if (param.method.getName().equals("onCellInfoChanged")){
                                param.args[0]=null;
                            }
                            if (param.method.getName().equals("onCellLocationChanged")){
                                param.args[0]=null;
                            }
                            return super.beforeMethod(param);
                        }

                        @Override
                        protected boolean afterMethod(MethodHookParam param) {
                            return super.afterMethod(param);
                        }

                        @Override
                        public void hook() {
                            Class[] interfaces = aclass.getInterfaces();
                            for (Class anInterface : interfaces) {
                                if (TelephonyCallback.CellInfoListener.class.equals(anInterface)) {
                                    hookAllMethod("onCellInfoChanged");
                                }
                                if (TelephonyCallback.CellLocationListener.class.equals(anInterface)){
                                    hookAllMethod("onCellLocationChanged");
                                }
                            }
                        }
                    };
                }
            }.safeHook();
        }
        XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
                "getCellLocation", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(null);
                        Log.d(TAG, "getCellLocation");
                    }
                });

        XposedHelpers.findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
                "onCellLocationChanged", CellLocation.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(null);
                        Log.d(TAG, "onCellLocationChanged");
                    }
                });

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
            XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
                    "getPhoneCount", new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (!isOpen) return;
                            param.setResult(1);
                            Log.d(TAG, "getPhoneCount");
                        }
                    });
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
                    "getNeighboringCellInfo", new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (!isOpen) return;
                            param.setResult(new ArrayList<>());
                            Log.d(TAG, "getNeighboringCellInfo");
                        }
                    });
        }

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
                    "getAllCellInfo", new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (!isOpen) return;
                            param.setResult(null);
                            Log.d(TAG, "getAllCellInfo");
                        }
                    });
            XposedHelpers.findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
                    "onCellInfoChanged", List.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (!isOpen) return;
                            param.setResult(null);
                            Log.d(TAG, "onCellInfoChanged");
                        }
                    });
        }

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getScanResults", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult(new ArrayList<>());
                Log.d(TAG, "getScanResults");
            }
        });

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getWifiState", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult(1);
                Log.d(TAG, "getWifiState");
            }
        });

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "isWifiEnabled", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult(true);
                Log.d(TAG, "isWifiEnabled");
            }
        });

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getMacAddress", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult("00-00-00-00-00-00-00-00");
                Log.d(TAG, "getMacAddress");
            }
        });

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getSSID", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult("null");
                Log.d(TAG, "getSSID");
            }
        });

        XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getBSSID", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult("00-00-00-00-00-00-00-00");
                Log.d(TAG, "getBSSID");
            }
        });


        XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
                "getTypeName", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult("WIFI");
                        Log.d(TAG, "getTypeName");
                    }
                });
        XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
                "isConnectedOrConnecting", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(true);
                        Log.d(TAG, "isConnectedOrConnecting");
                    }
                });

        XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
                "isConnected", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(true);
                        Log.d(TAG, "isConnected");
                    }
                });

        XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
                "isAvailable", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(true);
                        Log.d(TAG, "isAvailable");
                    }
                });

        XposedHelpers.findAndHookMethod("android.telephony.CellInfo", classLoader,
                "isRegistered", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        param.setResult(true);
                        Log.d(TAG, "isRegistered");
                    }
                });
    }

    private static void otherHook(ClassLoader classLoader) {
        XposedBridge.hookAllMethods(LocationManager.class, "getProviders", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                ArrayList<String> arrayList = new ArrayList<>();
                arrayList.add("gps");
                param.setResult(arrayList);
                Log.d(TAG, "getProviders");
            }
        });

        XposedHelpers.findAndHookMethod(LocationManager.class, "getBestProvider", Criteria.class, Boolean.TYPE, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult("gps");
                Log.d(TAG, "getBestProvider");
            }
        });

        XposedHelpers.findAndHookMethod(LocationManager.class, "addGpsStatusListener", GpsStatus.Listener.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                if (param.args[0] != null) {
                    XposedHelpers.callMethod(param.args[0], "onGpsStatusChanged", 1);
                    XposedHelpers.callMethod(param.args[0], "onGpsStatusChanged", 3);
                }
                Log.d(TAG, "addGpsStatusListener");
            }
        });

        XposedHelpers.findAndHookMethod(LocationManager.class, "addNmeaListener", GpsStatus.NmeaListener.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                if (!isOpen) return;
                param.setResult(false);
                Log.d(TAG, "addNmeaListener");
            }
        });
        try {
            XposedHelpers.findAndHookMethod("android.location.LocationManager", classLoader,
                    "getGpsStatus", GpsStatus.class, new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            if (!isOpen) return;
                            Log.d(TAG, "getGpsStatus");
                            GpsStatus gss = (GpsStatus) param.getResult();
                            if (gss == null)
                                return;

                            Class<?> clazz = GpsStatus.class;
                            Method m = null;
                            for (Method method : clazz.getDeclaredMethods()) {
                                if (method.getName().equals("setStatus")) {
                                    if (method.getParameterTypes().length > 1) {
                                        m = method;
                                        break;
                                    }
                                }
                            }
                            if (m == null)
                                return;

                            //access the private setStatus function of GpsStatus
                            m.setAccessible(true);

                            //make the apps belive GPS works fine now
                            int svCount = 5;
                            int[] prns = {1, 2, 3, 4, 5};
                            float[] snrs = {0, 0, 0, 0, 0};
                            float[] elevations = {0, 0, 0, 0, 0};
                            float[] azimuths = {0, 0, 0, 0, 0};
                            int ephemerisMask = 0x1f;
                            int almanacMask = 0x1f;

                            //5 satellites are fixed
                            int usedInFixMask = 0x1f;

                            XposedHelpers.callMethod(gss, "setStatus", svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
                            param.args[0] = gss;
                            param.setResult(gss);
                            try {
                                m.invoke(gss, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
                                param.setResult(gss);
                            } catch (Exception e) {
                                XposedBridge.log(e);
                            }
                        }
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }

        for (Method method : LocationManager.class.getDeclaredMethods()) {
            if (method.getName().equals("requestLocationUpdates")
                    && !Modifier.isAbstract(method.getModifiers())
                    && Modifier.isPublic(method.getModifiers())) {
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        Log.d(TAG, "requestLocationUpdates");
                        if (param.args.length >= 4 && (param.args[3] instanceof LocationListener)) {

                            LocationListener ll = (LocationListener) param.args[3];

                            Class<?> clazz = LocationListener.class;
                            Method m = null;
                            for (Method method : clazz.getDeclaredMethods()) {
                                if (method.getName().equals("onLocationChanged") && !Modifier.isAbstract(method.getModifiers())) {
                                    m = method;
                                    break;
                                }
                            }
                            Location l = getLocation();
                            XposedHelpers.callMethod(ll, "onLocationChanged", l);
                            try {
                                if (m != null) {
                                    m.invoke(ll, l);
                                }
                            } catch (Exception e) {
                                XposedBridge.log(e);
                            }
                        }
                    }
                });
            }

            if (method.getName().equals("requestSingleUpdate")
                    && !Modifier.isAbstract(method.getModifiers())
                    && Modifier.isPublic(method.getModifiers())) {
                XposedBridge.hookMethod(method, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        if (!isOpen) return;
                        Log.d(TAG, "requestSingleUpdate");
                        if (param.args.length >= 3 && (param.args[1] instanceof LocationListener)) {

                            LocationListener ll = (LocationListener) param.args[3];

                            Class<?> clazz = LocationListener.class;
                            Method m = null;
                            for (Method method : clazz.getDeclaredMethods()) {
                                if (method.getName().equals("onLocationChanged") && !Modifier.isAbstract(method.getModifiers())) {
                                    m = method;
                                    break;
                                }
                            }

                            try {
                                if (m != null) {
                                    Location l = getLocation();
                                    m.invoke(ll, l);
                                }
                            } catch (Exception e) {
                                XposedBridge.log(e);
                            }
                        }
                    }
                });
            }
        }


    }


    private static void hookGps(ClassLoader classLoader) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            new Hook(LocationManager.class.getName()) {
                @Override
                protected boolean beforeMethod(MethodHookParam param) {
                    if (param.method.getName().equals("requestLocationUpdates")) {
                        if (param.args.length == 4) {
                            Object obargs = param.args[3];
                            Object pClass = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{LocationListener.class}, new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                    if (method.getName().equals("onLocationChanged")) {
                                        if (args[0] instanceof List) {
                                            List<Location> data = (List<Location>) args[0];
                                            List<Location> locations = new ArrayList<Location>();
                                            for (Location datum : data) {
//                                                locations.add(getLocation());
                                                changeLocation(datum);
                                            }
                                            args[0] = locations;
                                        } else if (args[0] instanceof Location) {
//                                            args[0] = getLocation();
                                            changeLocation((Location) args[0]);
                                        }
                                        return method.invoke(obargs, args);
                                    }
                                    return method.invoke(obargs, args);
                                }
                            });
                            param.args[3] = pClass;
                        } else {
                            param.setResult(null);
                        }
                    }
                    return super.beforeMethod(param);
                }

                @Override
                protected boolean afterMethod(MethodHookParam param) {
                    if ("getLastKnownLocation".equals(param.method.getName()) || "getLastLocation".equals(param.method.getName())) {
                        Location result = (Location) param.getResult();
                        Log.e(TAG, "afterMethod: getLastKnownLocation location=" + result);
                        param.setResult(getLocation());
                    }

                    return super.afterMethod(param);
                }

                @Override
                public void hook() {
                    hookMethod("requestLocationUpdates", String.class, LocationRequest.class, Executor.class, LocationListener.class);

                    hookMethod("requestLocationUpdates", String.class, LocationRequest.class, PendingIntent.class);

                    hookMethod("getLastKnownLocation", String.class);
                    hookMethod("getLastLocation");
//                    hookAllMethod();
                }
            }.hook();
        } else {

            new Hook(LocationManager.class.getName()) {
                @Override
                protected boolean beforeMethod(MethodHookParam param) {
                    if (param.method.getName().equals("requestLocationUpdates")) {
                        if (param.args.length == 4) {
                            Object obargs = param.args[3];
                            Object pClass = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{LocationListener.class}, new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                    if (method.getName().equals("onLocationChanged")) {
                                        if (args[0] instanceof List) {
                                            List<Location> data = (List<Location>) args[0];
                                            List<Location> locations = new ArrayList<Location>();
                                            for (Location datum : data) {
//                                                locations.add(getLocation());
                                                changeLocation(datum);
                                            }
                                            args[0] = locations;
                                        } else if (args[0] instanceof Location) {
//                                            args[0] = getLocation();
                                            changeLocation((Location) args[0]);
                                        }
                                        return method.invoke(obargs, args);
                                    }
                                    return method.invoke(obargs, args);
                                }
                            });
                            param.args[3] = pClass;
                        } else {
                            param.setResult(null);
                        }
                    }
                    return super.beforeMethod(param);
                }

                @Override
                protected boolean afterMethod(MethodHookParam param) {
                    if (param.method.getName().equals("getLastKnownLocation") || param.method.getName().equals("getLastLocation")) {
                        Location result = (Location) param.getResult();
                        Log.e(TAG, "afterMethod: getLastKnownLocation location=" + result);

                        param.setResult(getLocation());
                    }

                    return super.afterMethod(param);
                }

                @Override
                public void hook() {
                    hookMethod("requestLocationUpdates", String.class, long.class, float.class, LocationListener.class);

                    hookMethod("getLastKnownLocation", String.class);
                    hookMethod("getLastLocation");
                }
            }.hook();
        }
        try {
            otherHook(classLoader);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static Location getLocation() {
        Location l = new Location(LocationManager.GPS_PROVIDER);
        setLocation(l);
        return l;
    }

    private static void changeLocation(Location location){
        if (location!=null){
            location.setLongitude(longitude);
            location.setLatitude(latitude);
            location.setAltitude(0.0);
        }
    }

    private static void setLocation(Location result) {
        if (result != null) {
            result.setLongitude(longitude);
            result.setLatitude(latitude);
            result.setAccuracy(100f);
            result.setAltitude(0.0);
            result.setTime(System.currentTimeMillis());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                result.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
            }

        }
    }

    private static double getRandom(double d) {
        Random random = new Random();
        int i = random.nextInt(3);
        if (i == 1) {
            return d;
        } else if (i == 2) {
            return -d;
        }
        return 0;
    }
}
